/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ohos.fresco.demo.okhttp3;

import ohos.eventhandler.EventRunner;
import ohos.utils.net.Uri;

import com.facebook.common.logging.FLog;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.producers.BaseNetworkFetcher;
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks;
import com.facebook.imagepipeline.producers.Consumer;
import com.facebook.imagepipeline.producers.FetchState;
import com.facebook.imagepipeline.producers.ProducerContext;
import com.oszc.bbhmlibrary.wrapper.SystemClock;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;

import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * okhttp网络访问者
 *
 * @author dev
 * @since 2021-07-29
 */
public class OkHttpNetworkFetcher extends BaseNetworkFetcher<OkHttpNetworkFetcher.OkHttpNetworkFetchState> {
    /**
     * 好的http网络获取状态
     *
     * @author dev
     * @since 2021-08-04
     */
    public static class OkHttpNetworkFetchState extends FetchState {

        /**
         * 提交时间
         */
        private long submitTime;
        /**
         * 响应时间
         */
        private long responseTime;
        /**
         * 获取完整的时间
         */
        private long fetchCompleteTime;

        /**
         * 好的http网络获取状态
         *
         * @param consumer 消费者
         * @param producerContext 生产环境
         */
        public OkHttpNetworkFetchState(Consumer<EncodedImage> consumer, ProducerContext producerContext) {
            super(consumer, producerContext);
        }

        /**
         * 有提交的时间
         *
         * @return long
         */
        public long getSubmitTime() {
            return submitTime;
        }

        /**
         * 提交时间
         *
         * @param submitTime 提交时间
         */
        public void setSubmitTime(long submitTime) {
            this.submitTime = submitTime;
        }

        /**
         * 得到的响应时间
         *
         * @return long
         */
        public long getResponseTime() {
            return responseTime;
        }

        /**
         * 设置响应时间
         *
         * @param responseTime 响应时间
         */
        public void setResponseTime(long responseTime) {
            this.responseTime = responseTime;
        }

        /**
         * 得到获取完整的时间
         *
         * @return long
         */
        public long getFetchCompleteTime() {
            return fetchCompleteTime;
        }

        /**
         * 获取完整的时间
         *
         * @param fetchCompleteTime 获取完整的时间
         */
        public void setFetchCompleteTime(long fetchCompleteTime) {
            this.fetchCompleteTime = fetchCompleteTime;
        }
    }

    private static final String TAG = "OkHttpNetworkFetchProducer";
    private static final String QUEUE_TIME = "queue_time";
    private static final String FETCH_TIME = "fetch_time";
    private static final String TOTAL_TIME = "total_time";
    private static final String IMAGE_SIZE = "image_size";

    private Call.Factory mCallFactory;

    private Executor mCancellationExecutor;

    /**
     * okhttp网络访问者
     *
     * @param okHttpClient 好的http客户端
     */
    public OkHttpNetworkFetcher(OkHttpClient okHttpClient) {
        this(okHttpClient, okHttpClient.dispatcher().executorService());
    }

    /**
     * okhttp网络访问者
     *
     * @param callFactory 打电话给工厂
     * @param cancellationExecutor 撤销遗嘱执行人
     */
    public OkHttpNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor) {
        mCallFactory = callFactory;
        mCancellationExecutor = cancellationExecutor;
    }

    @Override
    public OkHttpNetworkFetchState createFetchState(Consumer<EncodedImage> consumer, ProducerContext context) {
        return new OkHttpNetworkFetchState(consumer, context);
    }

    @Override
    public void fetch(OkHttpNetworkFetchState fetchState, Callback callback) {
        fetchState.submitTime = SystemClock.elapsedRealtime();
        final Uri uri = fetchState.getUri();

        try {
            Request request = new Request.Builder()
                .cacheControl(new CacheControl.Builder().noStore().build())
                .url(uri.toString())
                .get()
                .build();
            fetchWithRequest(fetchState, callback, request);
        } catch (Exception e) {
            callback.onFailure(e);
        }
    }

    @Override
    public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
        fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
    }

    @Override
    public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
        Map<String, String> extraMap = new HashMap<>(4);
        extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
        extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
        extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
        extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
        return extraMap;
    }

    /**
     * 获取与请求
     *
     * @param fetchState 获取状态
     * @param callback 回调
     * @param request 请求
     */
    protected void fetchWithRequest(OkHttpNetworkFetchState fetchState, Callback callback, Request request) {
        final Call call = mCallFactory.newCall(request);

        fetchState.getContext().addCallbacks(new BaseProducerContextCallbacks() {
            @Override
            public void onCancellationRequested() {
                cancelRequest(call);
            }
        });

        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                response(call, response, fetchState, callback);
            }

            @Override
            public void onFailure(Call call, IOException e) {
                handleException(call, e, callback);
            }
        });
    }

    private void response(Call call, Response response, OkHttpNetworkFetchState fetchState, Callback callback) throws IOException {
        fetchState.responseTime = SystemClock.elapsedRealtime();
        final ResponseBody body = response.body();
        try {
            if (!response.isSuccessful()) {
                handleException(call, new IOException("Unexpected HTTP code " + response), callback);
                return;
            }

            long contentLength = body != null ? body.contentLength() : 0;
            if (contentLength < 0) {
                contentLength = 0;
            }
            callback.onResponse(body != null ? body.byteStream() : null, (int) contentLength);
        }catch (Exception error){
            handleException(call, error, callback);
        } finally {
            try {
                if (body != null) {
                    body.close();
                }
            } catch (Exception error) {
                FLog.w(TAG, "Exception when closing response body", error);
            }
        }
    }

    private void cancelRequest(Call call) {
        if (EventRunner.current() != EventRunner.getMainEventRunner()) {
            call.cancel();
        } else {
            mCancellationExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    call.cancel();
                }
            });
        }
    }

    private void handleException(final Call call, final Exception e, final Callback callback) {
        if (call.isCanceled()) {
            callback.onCancellation();
        } else {
            callback.onFailure(e);
        }
    }
}
